 #include "config.h"
#include <fstream>

namespace anet {
	namespace utils {
		CConfig::CConfig() : m_root(nullptr) {
			memset(m_error, 0, sizeof(m_error));
		}

		CConfig::~CConfig() {
			freeNode(m_root);
		}

		void CConfig::freeNode(SNodeInfo* node) {
			// try to free all resource of node.
			if (node != nullptr) {
				for (const auto& e : node->name2Children) {
					freeNode(e.second);
				}
				delete node;
			}
		}

		bool CConfig::Parse(const std::string& filePath) {
			if (m_root != nullptr) {
				freeNode(m_root);
				memset(m_error, 0, sizeof(m_error));
				m_root = nullptr;
			}

			std::ifstream ifs;
			try {
				// open file with ifstream.
				ifs.open(filePath);
				if (!ifs) {
					std::snprintf(m_error, sizeof(m_error) - 1, "open %s error", filePath.c_str());
					return false;
				}
			} catch (...) {
				std::snprintf(m_error, sizeof(m_error) - 1, "open %s error", filePath.c_str());
				return false;
			}

			// stack for SNodeInfo*
			std::stack<SNodeInfo*> stk;

			// stack defer object.
			CDefer defer([&stk]() {
				while (!stk.empty()) {
					auto top = stk.top();
					delete top;
					stk.pop();
				}
			});

			int line = 0;
			while (!ifs.eof()) {
				std::string strline;
				getline(ifs, strline);
				if (!parseLine(stk, strline, line, defer)) {
					return false;
				}
			}

			return true;
		}

		// parse a line.
		bool CConfig::parseLine(std::stack<SNodeInfo*> &stk, const std::string &strLine, int &line, CDefer &defer) {
			// remove all \r\n\t and space.
			auto strline = trimSpace(strLine);

			line++;

			// ignore space line.
			if (strline.empty()) {
				// ignore space line.
				return true;
			}

			if (strline[0] == '#' || (strline.size() >= 2 && strline[0] == '/' && strline[1] == '/')) {
				// # and // is note line, just ignore it.
				return true;
			} else if (strline[0] == '<') {
				// new node start or end.
				auto pos = strline.find_first_of('>');
				if (pos == std::string::npos) {
					std::snprintf(m_error, sizeof(m_error) - 1, "%s has no '>' flag in line:%d", strline.c_str(), line);
					return false;
				}

				if (strline[1] == '/') {
					if (stk.empty()) {
						std::snprintf(m_error, sizeof(m_error) - 1, "there is no root node in line:%d", line);
						return false;
					}

					// end a node.
					std::string sName = strline.substr(2, pos - 2);
					sName = trimSpace(sName);

					// parent node.
					auto topNode = stk.top();
					if (sName != topNode->name) {
						std::snprintf(m_error, sizeof(m_error) - 1, "%s is not match with %s in line:%d", sName.c_str(), topNode->name.c_str(), line);
						return false;
					}

					if (stk.size() <= 1) {
						m_root = topNode;
					}
					stk.pop();
					if (stk.empty()) {
						defer.SetDeferFlag(false);
						return true;
					}
					return true;
				} else {
					// a new node.
					auto name = strline.substr(1, pos - 1);
					name = trimSpace(name);
					if (name.empty()) {
						std::snprintf(m_error, sizeof(m_error) - 1, "%s is empty in line:%d", strline.c_str(), line);
						return false;
					}

					if (!stk.empty()) {
						if (stk.top()->name2Children.find(name) != stk.top()->name2Children.end()) {
							std::snprintf(m_error, sizeof(m_error) - 1, "%s.%s node is repeated in line:%d", stk.top()->name.c_str(), strline.c_str(), line);
							return false;
						}

						SNodeInfo *newNode = new SNodeInfo();
						newNode->name = name;
						stk.top()->name2Children[name] = newNode;
						stk.push(newNode);
					} else {
						SNodeInfo *newNode = new SNodeInfo();
						newNode->name = name;
						stk.push(newNode);
					}
					return true;
				}
			} else {
				if (stk.empty()) {
					std::snprintf(m_error, sizeof(m_error) - 1, "there is no root in line:%d", line);
					return false;
				}

				// new data node.
				auto pos = strline.find_first_of('=');
				if (pos == std::string::npos) {
					std::snprintf(m_error, sizeof(m_error) - 1, "%s line has no '=' flag in line:%d", strline.c_str(), line);
					return false;
				}

				auto key = strline.substr(0, pos);
				key = trimSpace(key);
				auto value = strline.substr(pos + 1, strline.length() - pos);
				value = trimSpace(value);

				if (key.empty()) {
					std::snprintf(m_error, sizeof(m_error) - 1, "%s key is empty in line:%d", strline.c_str(), line);
					return false;
				}

				if (stk.top()->key2Value.find(key) != stk.top()->key2Value.end()) {
					std::snprintf(m_error, sizeof(m_error) - 1, "%s.%s key is repeated in line:%d", stk.top()->name.c_str(), key.c_str(), line);
					return false;
				}
				stk.top()->key2Value[key] = value;

				return true;
			}
		}

		bool CConfig::ParseContent(const std::string &content, const char* sep) {
			if (m_root != nullptr) {
				freeNode(m_root);
				memset(m_error, 0, sizeof(m_error));
				m_root = nullptr;
			}

			std::vector<std::string> vec;
			strSplits(content, sep, vec);

            // stack for SNodeInfo*
			std::stack<SNodeInfo*> stk;

			// stack defer object.
			CDefer defer([&stk]() {
				while (!stk.empty()) {
					auto top = stk.top();
					delete top;
					stk.pop();
				}
			});

			int line = 0;
			for (size_t i = 0; i < vec.size(); i++) {
				if (!parseLine(stk, vec[i], line, defer)) {
					return false;
				}
			}
			return true;
		}

		nodePair CConfig::GetNode(const std::string& path) {
			std::vector<std::string> vec;
			strSplits(path, "/", vec);
			return this->find(vec, m_root);
		}

		nodePair CConfig::find(std::vector<std::string>& vec, const SNodeInfo* node) {
			if (node == nullptr) {
				return { "",false };
			}

			if (vec.empty()) {
				return { "",false };
			}

			if (vec[0] == node->name) {
				if (vec.size() == 2) {
					auto it = node->key2Value.find(vec[1]);
					if (it != node->key2Value.end()) {
						return { it->second,true };
					} else {
						return { "",false };
					}
				} else {
					vec.erase(vec.begin());
					if (vec.empty()) {
						return { "",false };
					}

					auto tmp = node->name2Children.find(vec[0]);
					if (tmp != node->name2Children.end()) {
						return find(vec, tmp->second);
					} else {
						return { "",false };
					}
				}
			} else {
				return { "",false };
			}
		}

		std::string CConfig::GetLastError() {
			return std::string(m_error);
		}
	}
}
